#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BitwiseOre.SharedMemory.Core.Primatives;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Primatives
{
    [TestFixture]
    public sealed class ReadBufferFixture : AssertionHelper
    {
        [Test(Description = "Checks that the initial read byte call delegates to reading from the buffer " +
                            "contents at offset 0.")]
        public void ReadByte_StartsAtOffsetZero()
        {
            var contents = new Mock<IByteArray>();
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadByte();

            contents.Verify(c => c[It.Is<int>(i => i == 0)]);
        }

        [Test(Description = "Checks that the return value from reading a byte is the value from the buffer contents")]
        public void ReadByte_ReturnsValueFromBuffer()
        {
            var values = new byte[] {0x33, 0x41, 0x11, 0xab};
            var contents = new Mock<IByteArray>();
            contents.Setup(c => c[It.IsAny<int>()]).Returns((int i) => ((IList<byte>) values)[i]);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            var result = buffer.ReadByte();
            
            Expect(result, EqualTo(values[0]));
        }

        [Test(Description = "Checks that subsequent read byte calls delgates at subsequent offsets.")]
        public void ReadByte_ContiuesAtSubseqentOffsets()
        {
            var contents = new Mock<IByteArray>();
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadByte();
            buffer.ReadByte();
            
            contents.Verify(c => c[It.Is<int>(i => i == 1)]);
        }

        [Test(Description = "Checks that attempting to read a byte past the end of the buffer returns -1")]
        public void ReadByte_PastEndOfBuffer()
        {
            const int length = 2;
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.SetupGet(l => l.Length).Returns(length);

            var buffer = new ReadBuffer(layout.Object);
            for (var i = 0; i < length; i++)
            {
                buffer.ReadByte();
            }
            var result = buffer.ReadByte();

            Expect(result, EqualTo(-1));
        }

        [Test(Description = "Checks that the inital read of an array delegates to reading from offset 0")]
        public void ReadArraySegment_StartsAtOffsetZero()
        {
            var array = new ArraySegment<byte>(new byte[5]);
            var contents = new Mock<IByteArray>();
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadArraySegment(array);

            contents.Verify(c => c.ReadArraySegment(It.Is<int>(i => i == 0),
                                                    It.Is<ArraySegment<byte>>(s => s == array)),
                            "ReadArraySegment did not delegate to contents array as expected.");
        }

        [Test(Description = "Checks that reading an array returns the result of the delgated read of the contents")]
        public void ReadArraySegment_ReturnsResultOfDelegatedCall()
        {
            const int expectedresult = 5;
            var contents = new Mock<IByteArray>();
            contents.Setup(c => c.ReadArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns(expectedresult);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            var result = buffer.ReadArraySegment(new ArraySegment<byte>(new byte[5]));
            
            Expect(result, EqualTo(expectedresult), "Unexpected return value when reading the array.");
        }

        [Test(Description = "Checks that subsequent read array calls read from subsequent offsets.")]
        public void ReadArraySegment_ContinuesAtSubsequentOffsets()
        {
            const int initialsize = 5;
            const int subsequentsize = 3;
            var contents = new Mock<IByteArray>();
            contents.Setup(c => c.ReadArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns((int _, ArraySegment<byte> array) => array.Count);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(50);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[initialsize]));
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[subsequentsize]));
            
            contents.Verify(c => c.ReadArraySegment(It.Is<int>(i => i == initialsize),
                                                    It.IsAny<ArraySegment<byte>>()),
                            "Expected read of the buffer at the subsequent offset, but it didn't happen.");
        }

        [Test(Description = "Checks that attempting to read to past the end of the buffer delegates a " +
                            "partial read of the remainder of the buffer.")]
        public void ReadArraySegment_ToPastEndOfBuffer()
        {
            const int length = 9;
            const int initialsize = 5;
            const int subsequentsize = 7;
            var contents = new Mock<IByteArray>();
            contents.Setup(c => c.ReadArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns((int _, ArraySegment<byte> array) => array.Count);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(length);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[initialsize]));
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[subsequentsize]));

            contents.Verify(
                c => c.ReadArraySegment(It.IsAny<int>(),
                                        It.Is<ArraySegment<byte>>(seg => seg.Count == length - initialsize)),
                "Expected a parital read from the buffer contents, but it didn't happen.");
        }

        [Test(Description = "Checks that attempting to read an array while past the end of the buffer does " +
                            "not delegate to reading the contents.")]
        public void ReadArraySegment_WhenPastEndOfBuffer()
        {
            const int length = 9;
            const int initialsize = 9;
            const int subsequentsize = 7;
            var contents = new Mock<IByteArray>();
            contents.Setup(c => c.ReadArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns((int _, ArraySegment<byte> array) => array.Count);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(length);

            var buffer = new ReadBuffer(layout.Object);
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[initialsize]));
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[subsequentsize]));

            contents.Verify(c => c.ReadArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()),
                            Times.Once(),
                            "Unexpected delgated call to contents of the buffer.");
        }

        [Test(Description = "Clearing the read buffer sets the length of the buffer to 0.")]
        public void Clear_SetsLengthToZero()
        {
            var layout = new Mock<IBufferLayout>();

            var buffer = new ReadBuffer(layout.Object);
            buffer.Clear();

            layout.VerifySet(l => l.Length = 0);
        }

        [Test(Description = "Clearing the read buffer resets the current position.")]
        public void Clear_ResetsCurrentPostion()
        {
            const int firstlength = 5;
            const int secondlength = 1;
            var layout = new TestBufferLayout {Length = firstlength};

            var buffer = new ReadBuffer(layout);
            buffer.ReadArraySegment(new ArraySegment<byte>(new byte[5]));
            var waspastend = buffer.IsPastEnd;
            buffer.Clear();
            layout.Length = secondlength;

            Expect(waspastend, "ReadBuffer was not past the end prior to being cleared.");
            Expect(!buffer.IsPastEnd, "Buffer was past the end with a postive length after being cleared.");
        }


        private sealed class TestBufferLayout : IBufferLayout
        {
            private sealed class ByteArray : IByteArray
            {
                private readonly byte[] _array;

                public ByteArray()
                {
                    _array = Enumerable.Range(0, 256).Select(i => (byte) i).ToArray();
                }

                public byte this[int index]
                {
                    get { return _array[index]; }
                    set { _array[index] = value; }
                }

                public int Capacity
                {
                    get { return _array.Length; }
                }

                public int Count
                {
                    get { return _array.Length; }
                }

                public int ReadArraySegment(int index, ArraySegment<byte> destination)
                {
                    var i = index;
                    var j = destination.Offset;
                    while (i < _array.Length && j < destination.Offset + destination.Count)
                    {
                        destination.Array[j++] = _array[i++];
                    }
                    return i - index;
                }

                public int WriteArraySegment(int offset, ArraySegment<byte> source)
                {
                    throw new NotImplementedException();
                }
            }
            
            private readonly IByteArray _contents = new ByteArray();
            
            public int Length { get; set; }

            public IByteArray Contents
            {
                get { return _contents; }
            }
        }
    }
}